// Tencent is pleased to support the open source community by making
// 蓝鲸智云 - 监控平台/日志平台 (BlueKing - Monitor/Log) available.
// Copyright (C) 2017-2022 THL A29 Limited, a Tencent company. All rights reserved.
// Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
// You may obtain a copy of the License at http://opensource.org/licenses/MIT
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//

package tracesderiver

import (
	"go.opentelemetry.io/collector/model/pdata"

	"github.com/TencentBlueKing/bk-collector/define"
)

type Operator interface {
	Operate(*define.Record) *define.Record
}

func NewTracesOperator(conf Config) Operator {
	ch := NewConfigHandler(conf)
	return tracesOperator{
		dm: NewSpanDimensionMatcher(ch),
	}
}

type tracesOperator struct {
	dm DimensionMatcher
}

func (to tracesOperator) Operate(record *define.Record) *define.Record {
	pdTraces, ok := record.Data.(pdata.Traces)
	if !ok {
		return nil
	}

	mb := NewMetricsBuilder()
	resourceSpans := pdTraces.ResourceSpans()
	metricItems := map[string][]metricItem{}
	types := to.dm.Types()

	for i := 0; i < resourceSpans.Len(); i++ {
		instSpans := resourceSpans.At(i).InstrumentationLibrarySpans()
		resources := to.dm.MatchResource(resourceSpans.At(i))
		for j := 0; j < instSpans.Len(); j++ {
			spans := instSpans.At(j).Spans()
			for k := 0; k < spans.Len(); k++ {
				for _, t := range types {
					// 如果该 type 没有匹配到任何指标 直接跳过
					dim, ok := to.dm.Match(t.Type, spans.At(k))
					if !ok {
						continue
					}
					// 匹配 resource keys 并提取合并维度
					keys := to.dm.ResourceKeys(t.Type)
					for _, key := range keys {
						if v, exist := resources[key]; exist {
							dim[key] = v
						}
					}

					val := ExtractorMap[t.Type].Extract(spans.At(k))
					metricItems[t.MetricName] = append(metricItems[t.MetricName], metricItem{
						Val:        val,
						Ts:         spans.At(k).EndTimestamp(),
						Dimensions: dim,
					})
				}
			}
		}
	}

	for k, v := range metricItems {
		mb.Build(k, v...)
	}

	return &define.Record{
		RecordType:  define.RecordMetricsDerived,
		RequestType: define.RequestDerived,
		Token:       record.Token,
		Data:        mb.pbMetrics,
	}
}
